﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;

namespace TuxedoLib
{
	public abstract class AssetLoader
	{
		private byte[] CheckedData()
		{
			if (this.IsFinished)
			{
				if (this.HasError)
				{
					throw new Exception(this.ErrorMessage);
				}
				else if (this.Data == null)
				{
					throw new Exception("Unknown error occurred while retreiving file.");
				}
				else
				{
					return this.Data;
				}
			}
			else
			{
				throw new Exception("Cannot access data from incomplete AssetLoader.");
			}
		}

		// This will assume ASCII for everything and trim out the BOM.
		// That's not ideal.
		public string ConvertDataToText()
		{
			byte[] data = this.CheckedData();
			System.Text.StringBuilder output = new StringBuilder(); 
			int length = data.Length;
			for (int i = 0; i < length; ++i)
			{
				output.Append((char)data[i]);
			}
			return output.ToString();
		}

		public Image ConvertDataToImage()
		{
			byte[] data = this.CheckedData();
			System.Drawing.Bitmap dotNetImage;
			try
			{
				dotNetImage = new System.Drawing.Bitmap(new System.IO.MemoryStream(data));
			}
			catch (System.ArgumentException)
			{
				throw new Exception("Data is not a valid image file.");
			}

			// TODO: verify that image is a PNG or JPG
			int width = dotNetImage.Width;
			int height = dotNetImage.Height;
			try
			{
				System.Drawing.Imaging.BitmapData bmpData = dotNetImage.LockBits(new System.Drawing.Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
				int byteCount = bmpData.Stride * height;
				byte[] bytes = new byte[byteCount];

				System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bytes, 0, byteCount);

				dotNetImage.UnlockBits(bmpData);

				Image output = new Image(width, height);
				int[] pixels = output.pixels;
				for (int i = bytes.Length - 4; i >= 0; i -= 4)
				{
					pixels[i >> 2] =
						((int)bytes[i + 3] << 24) |
						((int)bytes[i + 2] << 16) |
						((int)bytes[i + 1] << 8) |
						((int)bytes[i + 0]);
				}
				return output;
			}
			catch (Exception)
			{
				throw new Exception("An error occurred while loading bitmap data.");
			}
		}

		public static AssetLoader LoadFromFile(string path, bool asynchronous)
		{
			AssetLoader loader = new FileAssetLoader(path);
			return asynchronous ? new AsynchronousLoader(loader) : Resolve(loader);
		}

		public static AssetLoader LoadFromWeb(string url, int retries, bool asynchronous)
		{
			AssetLoader loader = new WebAssetLoader(url, retries);
			return asynchronous ? new AsynchronousLoader(loader) : Resolve(loader);
		}

		public static AssetLoader LoadFromProject(string path, Assembly assembly, bool asynchronous)
		{
			AssetLoader loader = new EmbeddedAssetLoader(path, assembly);
			return asynchronous ? new AsynchronousLoader(loader) : Resolve(loader);
		}

		private static AssetLoader Resolve(AssetLoader loader)
		{
			loader.LoadData();
			return loader;
		}

		public bool IsFinished { get; internal set; }
		public bool HasError { get; internal set; }
		public string ErrorMessage { get; internal set; }

		internal byte[] Data { get; set; }

		internal abstract byte[] RetrieveData();

		public void LoadData()
		{
			this.Data = null;
			try
			{
				byte[] data = this.RetrieveData();
				if (data != null)
				{
					this.Data = data;
					this.IsFinished = true;
				}
			}
			catch (Exception e)
			{
				this.IsFinished = true;
				this.HasError = true;
				this.ErrorMessage = e.Message;
			}
		}

		private class AsynchronousLoader : AssetLoader
		{
			private BackgroundWorker worker;

			public AsynchronousLoader(AssetLoader syncLoader)
			{
				this.worker = new BackgroundWorker();
				this.worker.DoWork += (sender, e) =>
				{
					syncLoader.LoadData();
				};

				this.worker.RunWorkerCompleted += (sender, e) =>
				{
					this.IsFinished = true;
					this.HasError = syncLoader.HasError;
					this.ErrorMessage = syncLoader.ErrorMessage;
					this.Data = syncLoader.Data;
				};
			}

			internal override byte[] RetrieveData()
			{
				this.worker.RunWorkerAsync();
				return null;
			}
		}

		private class EmbeddedAssetLoader : AssetLoader
		{
			private Assembly assembly;
			private string path;

			public EmbeddedAssetLoader(string path, Assembly assembly)
			{
				this.assembly = assembly;
				this.path = path;
			}

			internal override byte[] RetrieveData()
			{
				// directory canonicalizes -'s to _'s. File name does not.
				string[] pathFixer = this.path.Split('/');
				for (int i = 0; i < pathFixer.Length - 1; ++i)
				{
					pathFixer[i] = pathFixer[i].Replace('-', '_');
				}

				this.path = string.Join("/", pathFixer);
				string assemblyName = this.assembly.FullName.Split(',')[0];
				string funkyPath = assemblyName + "." + this.path.Replace('/', '.').Replace('\\', '.');
				System.IO.Stream stream = assembly.GetManifestResourceStream(funkyPath);
				if (stream == null)
				{
					throw new Exception("That file does not exist in your project. Is the file's mode set to 'Embedded Resource'?");
				}

				byte[] buffer = new byte[100];
				List<byte> bytes = new List<byte>();
				int bytesRead = 1;
				while (bytesRead > 0)
				{
					bytesRead = stream.Read(buffer, 0, buffer.Length);
					for (int i = 0; i < bytesRead; ++i)
					{
						bytes.Add(buffer[i]);
					}
				}

				return bytes.ToArray();
			}
		}

		private class FileAssetLoader : AssetLoader
		{
			private string path;
			public FileAssetLoader(string path)
			{
				this.path = path;
			}

			internal override byte[] RetrieveData()
			{
				if (System.IO.File.Exists(this.path))
				{
					return System.IO.File.ReadAllBytes(this.path);
				}
				else
				{
					throw new Exception("File '" + this.path + "' does not exist.");
				}
			}
		}

		private class WebAssetLoader : AssetLoader
		{
			private string url;
			private int retries;
			public WebAssetLoader(string url, int retries)
			{
				this.url = url;
				this.retries = retries;
			}

			internal override byte[] RetrieveData()
			{
				System.Net.WebClient wc = new System.Net.WebClient();
				try
				{
					return wc.DownloadData(this.url);
				}
				catch (System.Net.WebException)
				{
					throw new Exception("Could not connect to server to retrieve '" + this.url + "'. Check the antenna on the roof.");
				}
			}
		}
	}
}
